ShowTable of Contents
Introduction
The IBM® Lotus® Notes® ID Vault feature helps users by automatically collecting user ID files, so as to have centralized ID management in Lotus Domino®. Using the ID Vault, we can reset passwords and recover IDs easily; moreover, the ID Vault has removed the need to visit desktops for ID management.
In some cases, however, there may instances when the user ID files might not be collected due to incorrect configurations or some flaws from the administration end. Administrators must then manually check the Vault database to determine whether it contains IDs for all the current users. This can be an easy task, if there a only a few users; however, it can be cumbersome to find missing IDs when there are many users.
To overcome this we propose a solution called the Vault Database Scanner, which can automatically scan the Vault database and the Names.nsf file and report IDs missing from the Vault database.
The tool has been developed as a plug-and-play type in that you don't need to configure the tool to make it operational; all you need to do is deploy the tool and run the desired agent.
Get the database
Please note that since I could not upload a zip or ntf file, you need to get the attachment I was able to upload: VaultScanner-v1r4.zip.pdf. Just download it (it's at the bottom of the article in the attachments section) and remove the pdf extension to get the zip which you can expand.
Note that the latest version, 1.5, uses Rich text for the result lists, and outputs only 1 document per run of the agent. Caveat: results from 1.4 displayed in 1.5 do not display nicely.
Understanding the Vault Database Scanner
The database contains one LotusScript agent called "scanVault". The scanVault agent is housed in the LotusScript script library 'libVaultSCanner' and consists of two main functions called getVaultDocuments and compareNABVaultDB. It was rewritten using Lists and objects instead of temporary documents because it was unable to perform with more than a couple records in the NAB or ID Vault.
Create Configuration Document agent
This agent is obsolete. The scanVault agent looks for a Public AddressBook in the current NotesSession, and uses that to look for the ID Vault config documents. So as long as you are online with a Domino server and can read the NAB and ID Vault you should be OK.
scanVault agent
The scanVault agent consists of two main functions called getVaultDocuments and compareNABVaultDB.
The getVaultDocuments function gets the ID Vault configuration by creating an object of custom class clVaults.
The New subroutine in the class clVaults uses the current NotesSession object property AddressBooks to find a Public AddressBook, reads the ID Vault configuration from it, and creates a list of objects of custom class clVaultConfig. The clVaultConfig objects know how to read the ID vault configuration documents and extract the server name and path to the ID Vault. The requirement that the VauitScanner application needs to be on a server has been removed by removing the leading / of the ID Vault path. Note that the current implementation just uses the first server from the list of servers in the ID vault configuration document. For complex environments, this could be enhanced by scanning the list of servers to see if the server which we are using for the NAB is somehwhere in the list, assuming that the current Public directory provider is a server near us.
Once the ID Vault configuration has been read, getVaultDocuments creates an object to hold the names in the IDVault(s), and opens all the individual Vault databases, reads the list of active IDs and the list of inactive IDs. The only field read is the field IDOwner, which contains one or more names. The first name is taken to be the main one, and the others are taken to be aliases and marked as such in the list of IDs.
The routine saves and prints a line of statistics at the end.
Listing 1. Code for getVaultDocuments
Const IT_IDVaultPath = "VTPath"
Const IT_VaultServerList = "VaultServerList"
Const VW_IDVAULT = "IDVaults"
Const FRM_Result = "frmresult"
Const IT_Form = "Form"
Const IT_MissingActive = "fldmissingvault"
Const IT_Inactive = "fldVaultInactive"
Const IT_MissingBoth = "fldVaultMissingBoth"
Dim s As NotesSession
Dim db As NotesDatabase
Dim docResult as NotesDocument
Dim vw As NotesView
Dim dbNAB As NotesDatabase
Dim vwNAB As NotesView
Dim vaults As clVaults
Dim IDs As clVaultIDs' clVaults' clVaultConfig' clVaultID ' clVaults
%REM
Class clVaultConfig
Description: helper class containing vault stuff
%END REM
Class clVaultConfig
Public Path As String
Public Server As String
%REM
Sub New
Description: parse a vault doc from the NAB into a useable object
%END REM
Sub New(doc As NotesDocument)
Path = Replace(doc.GetItemValue(IT_IDVaultPath)(0), "\", "/")
If Left$(Path, 1) = "/" Then
path = Mid$(path, 2)
End If
' TODO try to match server with current server
Server = doc.GetItemValue(IT_VaultServerList)(0) ' just use first server
End Sub ' New
End Class
%REM
Class clVaultID
Description: helper class containing vault ID stuff
%END REM
Class clVaultID
Public IDOwner As String
Public isAlias As Boolean
Public mainName As String ' abbreviated main name for which this name is an alias
End Class
%REM
Class clVaults
Description: Comments for Class
%END REM
Class clVaults
Public Vaults List As clVaultConfig
Public numVaults As Long
%REM
Sub New
Description: Comments for Sub
%END REM
Sub New
Dim docNAB As NotesDocument
Set dbNAB = Nothing
numVaults = 0
Set s = New NotesSession
If Not IsEmpty(s.AddressBooks) Then
ForAll vDbNAB In s.AddressBooks
If vDbNAB.IsPublicAddressBook Then
Call vDbNAB.Open("", "")
If vDbNAB.IsOpen Then
Set dbNAB = vDbNAB
Exit ForAll
Else
MsgBox "Unable to open the Public addressBook found in NotesSession, make sure you are in an online session"
Exit Sub
End If
End If
End ForAll
End If
If dbNAB Is Nothing Then
MsgBox "No Public addressBook found in NotesSession, make sure you are in an online session"
Exit Sub
End If
Set vwNAB = dbNAB.GetView(VW_IDVAULT)
If Not vwNAB Is Nothing Then
Set docNAB = vwNAB.GetFirstDocument
Do Until docNAB Is Nothing
numVaults = numVaults + 1
Set vaults(numVaults) = New clVaultConfig(docNAB)
Set docNAB = vwNAB.GetNextDocument(docNAB)
Loop
End If
End Sub
End Class
%REM
Class clVaultIDs
Description: container to handle VaultIDs
%END REM
Class clVaultIDs
Public IDs List As clVaultID
Public inactiveIDs List As clVaultID
Public IDsNoAliases List As String
Public Aliases List As String
Public numIDs As Long
Public numInactiveIDs As Long
Public numActiveIDs As Long
Public numMainIDs As Long
Public statsGetVault As String
Public statsCompare As String
%REM
Function addID
Description: parse an IDVault viewentry
%END REM
Public Function addID(ve As NotesViewEntry) As Boolean
' expects a notesViewEntry from the view 'Vault Users' in an ID Vault db
Dim v ' will contain 1 or more Abbreviated names
Dim isAlias As Boolean
Dim nmMain$
addID = False
v = ve.Columnvalues(0) ' can be multivalue
numActiveIDs = numActiveIDs + 1
isAlias = False
If IsArray(v) Then
addID = True
ForAll vNam In v
If Not isAlias Then nmMain = vNam
' if one of the calls returns false, it will make addID false, signaling a failure
addID = addID And addName(IDs, CStr(vNam), isAlias, nmMain)
isAlias = True
End ForAll
Else
addID = addName(IDs, CStr(v), False, "")
End If
End Function ' addID
%REM
Function addName
Description: adds a name if not already in the list
%END REM
Function addName(theList List As clVaultID, abbName$, isAlias As Boolean, mainName$) As Boolean
Dim tID As clVaultID
Dim nm As NotesName
Dim lName As String
addName = False
lName = LCase$(abbname)
If Not IsElement(theList(lName)) Then
numIDs = numIDs + 1
Set nm = New NotesName(lName)
Set tID = New clVaultID
Set theList(lName) = tID
tID.IDOwner = nm.Canonical
tID.isAlias = isAlias
If isAlias Then
tID.mainName = mainName
If IsElement(Aliases(lName)) Then
Print "Skipping add to list of Aliases for " + abbName + ", already in the list"
Else
Aliases(lName) = mainName
End If
Else
numMainIDs = numMainIDs + 1
tID.mainName = abbName
If Not IsElement(IDsNoAliases(lName)) Then
IDsNoAliases(lName) = abbName
Else
Print "Skipping add to list of NoAlias for " + abbName + ", already in the list"
End If
End If
Else
Print "Skipping add for " + abbName + ", already in the list"
Exit Function
End If
addName = True
End Function ' addName
%REM
Function addIDinactive
Description: parse an inactive IDVault viewentry
%END REM
Public Function addIDinactive(ve As NotesViewEntry) As Boolean
' expects a notesViewEntry from the view 'Inactive User IDs' in an ID Vault db
Dim v ' will contain 1 or more Abbreviated names
Dim isAlias As Boolean
Dim nmMain As String
addIDinactive = False
v = ve.Columnvalues(1) ' can be multivalue
numInactiveIDs = numInactiveIDs + 1
isAlias = False
If IsArray(v) Then
addIDinactive = True
ForAll vNam In v
If Not isAlias Then nmMain = vNam
' if one of the calls returns false, it will make addIDinactive false, signaling a failure
addIDinactive = addIDinactive And addName(inactiveIDs, CStr(vNam), isAlias, nmMain)
isAlias = True
End ForAll
Else
addIDinactive = addName(inactiveIDs, CStr(v), False, "")
End If
End Function ' addIDinactive
End Class ' clVaultIDs
%REM
Function getVaultDocuments
Description: Uses the NotesSession to get a handle on the Public NAB
gets the ID Vault config from the NAB, and reads the active users from the ID Vaults it finds
returns True if no errors were encountered
%END REM
Function getVaultDocuments As Boolean
getVaultDocuments = False
Dim i As Long
Dim dbVault As NotesDatabase
Dim vwVault As NotesView
Dim OpenVaultDoc As NotesDocument
Dim vec As NotesViewEntryCollection
Dim ve As NotesViewEntry
Dim vt As clVaultConfig
Set vaults = New clVaults
If vaults.numVaults = 0 Then
MsgBox "No vaults found"
Exit Function
End If
Set IDs = New clVaultIDs
' read active and inactive users fom vaults
For i = 1 To vaults.numVaults
Set vt = vaults.Vaults(i)
Set dbVault = s.GetDatabase(vt.Server, vt.Path)
If Not dbVault.IsOpen Then
MsgBox "Could not open vault db: " + vt.Server + " !! " + vt.Path
Else
' read active users
Set vwVault = dbVault.getView("Vault Users")
Set vec = vwVault.Allentries
Set ve = vec.GetFirstEntry
Do Until ve Is Nothing
If Not IDs.addID(ve) Then
MsgBox "Aborting, an error occurred while adding a name from the ID Vault"
Exit Function
End If
Set ve = vec.GetNextEntry(ve)
Loop
' read inactive users
Set vwVault = dbVault.getView("Inactive User Ids")
Set vec = vwVault.Allentries
Set ve = vec.GetFirstEntry
Do Until ve Is Nothing
If Not IDs.addIDinactive(ve) Then
MsgBox "Aborting, an error occurred while adding a name from the ID Vault"
Exit Function
End If
Set ve = vec.GetNextEntry(ve)
Loop
End If
Next i
' print statistics
IDs.statsGetVault = "Found " & IDs.numActiveIDs & " active IDs, " & IDs.numInactiveIDs & " inactive IDs, " & IDs.numMainIDs & " IDs with " & IDs.numIDs & " names"
Print IDs.statsGetVault
getVaultDocuments = True
End Function ' getVaultDocuments
The
compareNABVaultDB function compares the users in the Address book with the list of IDs obtained from the ID Vault(s). It reports any user who doesn't have a Person entry in the list of active usersand of the persons who obnly have an inactive ID (see listing 2). Because the report might grow too long, the output is split up with another custom class so repetitive and error prone code is avoided.
The routine saves and prints a line of statistics at the end. The statistics are also saved in the results document(s) and used for display in the view.
Listing 2. Code for compareNABVaultDB function
%REM
Sub compareNABVaultDB
Description: Comments for Function
%END REM
Sub compareNABVaultDB
Dim docNAB As NotesDocument
Dim docResult As NotesDocument
Dim personDocName As String
Dim nm As NotesName
Dim notInActiveUsers List As String
Dim inInactiveUsers List As String
Dim notInVault List As String
Dim numNotActive As Long
Dim numNotInVault As Long
Dim numInactive As Long
Dim numActive As Long
Dim numNAB As Long
Dim isFinished As Boolean
Dim v As Variant
Dim isActive As Boolean
Dim isInactive As Boolean
Dim personMain As String
Dim multipleCanonical As Boolean
Dim foundCanonical As Boolean
Dim nmMain As NotesName
Dim rtInactive As NotesRichTextItem
Dim rtBoth As NotesRichTextItem
Dim rtVault As NotesRichTextItem
' scan people view, and check with ID Vault entries
Set vwNAB = dbNAB.Getview("People")
Set docNAB = vwNab.GetFirstDocument
Do Until docNAB Is Nothing
If docNab.IsValid And docNab.Size > 0 And Not docNab.IsDeleted Then
numNAB = numNAB + 1
v = docNAB.FullName
isActive = False
isInactive = False
multipleCanonical = False
foundCanonical = False
personMain = ""
Set nmMain = New NotesName(v(0))
ForAll vName In v
Set nm = New NotesName(vName)
If LCase$(Left$(vName, 3)) = "cn=" Then
If foundCanonical Then
multipleCanonical = True
Else
Set nmMain = nm
foundCanonical = True
End If
End If
persondocname = LCase$(nm.Abbreviated)
If IsElement(ids.ids(persondocname)) Then ' person doc but no active ID
isActive = True
Exit ForAll ' done, person has an active ID
ElseIf IsElement(ids.inactiveIDs(persondocname)) Then ' person doc with inactive ID
If Not isInactive Then ' only if we did not already find an inactive ID for this person
numInactive = numInactive + 1
inInactiveUsers(numInactive) = ids.inactiveIDs(persondocname).mainName
isInactive = True
End If
' don't exit the loop, this may be an old name
End If
End ForAll
If isActive Then 'no active ID found for any of the names
numActive = numActive + 1
If isInactive Then ' found a person with an Active ID for which one of the aliases has an inactive ID
' => remove false negative
Erase inInactiveUsers(numInactive)
numInactive = numInactive - 1
End If
Else
personMain = nmMain.Abbreviated
numNotActive = numNotActive + 1
notInActiveUsers(numNotActive) = personMain
If isInactive Then
' already done above
Else ' person doc also without inactive ID
numNotInVault = numNotInVault + 1
notInVault(numNotInVault) = personMain
End If
End If
End If
Set docNAB = vwNAB.GetNextDocument(docNAB)
Loop
' statistics
IDs.statsCompare = "Found " & numNAB & " person documents, of which " & numActive & " have Active IDs, " & numInactive & " have inactive IDs, " & numNotInVault & " have no record in the IDVault"
Print IDs.statsCompare
' output results
Call docResult.ReplaceItemValue(IT_Form, FRM_Result)
Call docResult.Replaceitemvalue("statsGetVault", IDs.statsGetVault)
Call docResult.Replaceitemvalue("statsCompare", IDs.statsCompare)
Set rtInactive = docResult.CreateRichTextItem(IT_Inactive)
Set rtBoth = docResult.CreateRichTextItem(IT_MissingBoth)
Set rtVault = docResult.CreateRichTextItem(IT_MissingActive)
Call dumpListToRT(inInactiveUsers, rtInactive)
Call dumpListToRT(notInVault, rtBoth)
Call dumpListToRT(notInActiveUsers, rtVault)
End Sub ' compareNABVaultDB
Listing 3 shows the main Script in the agent.
Listing 3. Script main
%REM
Sub agScan
Description: agent that compares
%END REM
Public Sub agScan
Dim tm As Single
tm = Timer
Dim s As New NotesSession
Set db = s.CurrentDatabase
Set docResult = db.CreateDocument
If getVaultDocuments Then
Call compareNABVaultDB
End If
Call docResult.ReplaceItemValue(IT_runTime, Round(Timer-tm, 2))
Call docResult.Save(True, True)
Print "scanVaults took"; Round(Timer-tm, 2); "seconds";
End Sub ' agScan